﻿using System;

namespace Kyoh
{
	/// <summary>
	/// 白色雑音・有色雑音を発生するクラスを定義します。
	/// </summary>
	public class Noise
	{
		#region フィールド
		const double PI2 = Math.PI * 2;
		double myDeviation,	// 標準偏差
				myAverage,	// 平均
				myFeedBack,	// 有色フィードバック値
				myResult;	// 発生した雑音

		Random myRandom;
		#endregion

		#region コンストラクタ
		/// <summary>
		/// 既定の標準偏差・平均値で(標準偏差=1, 平均=0)白色雑音を発生する新しい Noise クラスのインスタンスを初期化します。
		/// </summary>
		public Noise() : this(1.0, 0.0, 0.0) { }
		/// <summary>
		/// 指定された標準偏差・平均値で白色雑音を発生する新しい Noise クラスのインスタンスを初期化します。
		/// </summary>
		/// <param basename="standardDeviation">発生する雑音の標準偏差値。</param>
		/// <param basename="average">発生する雑音の平均値。</param>
		public Noise(double standardDeviation, double average) : this(standardDeviation, average, 0.0) { }
		/// <summary>
		/// 既定の標準偏差・平均値で指定された係数の1次フィードバックを行って有色雑音を発生する新しい Noise クラスのインスタンスを初期化します。
		/// </summary>
		/// <param basename="feedBack">白色雑音を有色化させる1次フィードバックの係数。</param>
		public Noise(double feedBack) : this(1.0, 0.0, feedBack) { }
		/// <summary>
		/// 既定の標準偏差・平均値で白色雑音を発生する新しい Noise クラスのインスタンスを初期化します。
		/// </summary>
		/// <param basename="rand_seed">疑似乱数系列の開始値を計算するために使用する数値。</param>
		public Noise(int randSeed)
			: this(1.0, 0.0, 0.0)
		{ myRandom = new Random(randSeed); }
		/// <summary>
		/// 指定された標準偏差・平均値で指定された係数の1次フィードバックを行って有色雑音を発生する新しい Noise クラスのインスタンスを初期化します。
		/// </summary>
		/// <param basename="standard_deviation">発生する雑音の標準偏差値。</param>
		/// <param basename="average">発生する雑音の平均値。</param>
		/// <param basename="feed_back">白色雑音を有色化させる1次フィードバックの係数。</param>
		public Noise(double standardDeviation, double average, double feedBack)
		{
			myDeviation = standardDeviation;
			myAverage = average;
			myFeedBack = feedBack;
			myRandom = new Random();
		}
		/// <summary>
		/// 指定された標準偏差・平均値で白色雑音を発生する新しい Noise クラスのインスタンスを初期化します。
		/// </summary>
		/// <param basename="standard_deviation">発生する雑音の標準偏差値。</param>
		/// <param basename="average">発生する雑音の平均値。</param>
		/// <param basename="rand_seed">疑似乱数系列の開始値を計算するために使用する数値。</param>
		public Noise(double standardDeviation, double average, int randSeed)
			: this(standardDeviation, average, 0.0)
		{ myRandom = new Random(randSeed); }
		/// <summary>
		/// 既定の標準偏差・平均値で指定された係数の1次フィードバックを行って有色雑音を発生する新しい Noise クラスのインスタンスを初期化します。
		/// </summary>
		/// <param basename="feed_back">白色雑音を有色化させる1次フィードバックの係数。</param>
		/// <param basename="rand_seed">疑似乱数系列の開始値を計算するために使用する数値。</param>
		public Noise(double feedBack, int randSeed)
			: this(1.0, 0.0, feedBack)
		{ myRandom = new Random(randSeed); }
		/// <summary>
		/// 指定された標準偏差・平均値で指定された係数の1次フィードバックを行って有色雑音を発生する新しい Noise クラスのインスタンスを初期化します。
		/// </summary>
		/// <param basename="standard_deviation">発生する雑音の標準偏差値。</param>
		/// <param basename="average">発生する雑音の平均値。</param>
		/// <param basename="feed_back">白色雑音を有色化させる1次フィードバックの係数。</param>
		/// <param basename="rand_seed">疑似乱数系列の開始値を計算するために使用する数値。</param>
		public Noise(double standardDeviation, double average, double feedBack, int randSeed)
			: this(standardDeviation, average, feedBack)
		{ myRandom = new Random(randSeed); }
		#endregion

		#region プロパティ
		/// <summary>
		/// 発生する雑音の標準偏差を取得・設定します。
		/// </summary>
		public double Deviation
		{
			get { return myDeviation; }
			set
			{
				if (double.IsInfinity(value) || double.IsNaN(value))
					throw new NotFiniteNumberException(value);
				myDeviation = value;
			}
		}
		/// <summary>
		/// 発生する雑音の平均値を取得・設定します。
		/// </summary>
		public double Average
		{
			get { return myAverage; }
			set
			{
				if (double.IsInfinity(value) || double.IsNaN(value))
					throw new NotFiniteNumberException(value);
				myAverage = value;
			}
		}
		/// <summary>
		/// 発生する雑音に対する1次フィードバック係数を取得・設定します。
		/// </summary>
		public double FeedBack
		{
			get { return myFeedBack; }
			set
			{
				if (double.IsInfinity(value) || double.IsNaN(value))
					throw new NotFiniteNumberException(value);
				myFeedBack = value;
				myResult = 0;
			}
		}
		#endregion

		#region 雑音発生
		/// <summary>
		/// 一度発生させた雑音値をもう一度呼び出します。
		/// </summary>
		/// <returns></returns>
		public double Prev()
		{
			return myResult;
		}
		/// <summary>
		/// 雑音値を発生します。
		/// </summary>
		/// <returns>次の雑音値</returns>
		public double Next()
		{
			double tmp;
			do
			{
				tmp = Math.Sqrt(-2 * Math.Log(myRandom.NextDouble()))
					* Math.Sin(myRandom.NextDouble() * PI2)
					* myDeviation
					+ myAverage

					+ (myResult * myFeedBack);
			}
			while (double.IsInfinity(tmp) || double.IsNaN(tmp));

			return (myResult = tmp);
		}
		/// <summary>
		/// 指定された浮動小数点数配列の要素に雑音値を格納します。
		/// </summary>
		/// <param basename="buffer">雑音値を格納する配列。</param>
		public void NextDoubles(double[] buffer)
		{
			for (int i = 0; i < buffer.Length; i++)
				buffer[i] = Next();
		}
		/// <summary>
		/// 指定された長さの、要素に雑音値を格納した浮動小数点数配列を取得します。
		/// </summary>
		public double[] NextDoubles(int length)
		{
			double[] buffer = new double[length];
			for (int i = 0; i < length; i++)
				buffer[i] = Next();
			return buffer;
		}
		#endregion
	}
}
